# Author: Hui ZHENG 
# Lisence: MIT

import uno
import unohelper

from com.sun.star.task import XJobExecutor

RegExp_3gpp_Ref = r'\[[:digit:]+\]'
RegExp_3gpp_Ref_Definition = r'^[:space:]*\[[:digit:]+\]'
RegExp_Numbering = r'[:digit:]+([. ][:digit:]+)+[:alpha:]?(-[:alnum:]+)?|[:digit:]+(-[:alnum:]+)'
RegExp_Figure_Prefix = r'Figure'
RegExp_Table_Prefix = r'Table'

def make3gppRefBookmarkName(index):
  return '3gpp_ref_%d' % index

def makeHeadingBookmarkName(numbering):
  return 'Heading_%s' % numbering.replace('.', '_').replace(' ', '_').strip()

def makeFigureBookmarkName(prefix, numbering):
  return '%s_%s' % (prefix, numbering.replace('.', '_').replace('-', '__').strip())

def connect(port):
  localContext = uno.getComponentContext()

  # create the UnoUrlResolver
  resolver = localContext.ServiceManager.createInstanceWithContext(
          "com.sun.star.bridge.UnoUrlResolver", localContext )

  # connect to the running office
  ctxt = resolver.resolve( "uno:socket,host=localhost,port=%d;urp;StarOffice.ComponentContext" % port)
  smgr = ctxt.ServiceManager

  # get the central desktop object
  desktop = smgr.createInstanceWithContext( "com.sun.star.frame.Desktop", ctxt)

  return (ctxt, desktop)

def tryToSyncData(ctxt):
  ctxt.ServiceManager

def fixHeadingOutlineLevel(document):
  para_styles = document.StyleFamilies.getByName("ParagraphStyles")
  for i in range(1, 9):
    heading = para_styles.getByName('Heading %d' % i)
    if heading.OutlineLevel != i:
      heading.OutlineLevel = i

def fixTableTextWrapMode(document):
  frames = document.TextFrames
  for i in range(0, frames.Count):
    frame = frames.getByIndex(i)
    try:
      frame.Start # would raise exception if the frame contains a table
    except:
      frame.TextWrap = 'NONE'

def findAllByRegularExpression(document, keyword):
  search = document.createSearchDescriptor()
  search.SearchRegularExpression = True
  search.SearchString = keyword
  return document.findAll(search)

def findAllByStyle(document, style_name):
  search = document.createSearchDescriptor()
  search.SearchStyles = True
  search.SearchString = style_name
  return document.findAll(search)

def replaceBookmark(document, text_range, bookmark_name, replace_old=True):
    bookmark = document.createInstance("com.sun.star.text.Bookmark") 
    bookmark.Name = bookmark_name
    if document.Bookmarks.hasByName(bookmark_name) and replace_old:
      old_bookmark = document.Bookmarks.getByName(bookmark_name)
      document.Text.removeTextContent(old_bookmark)
    text_range.Text.insertTextContent(text_range, bookmark, True)

def insertHyperLink(document, text_range, bookmark_name):
  if document.Bookmarks.hasByName(bookmark_name):
    text_range.HyperLinkURL = '#' + bookmark_name
    return True
  else:
    return False

def addBookmarksTo3gppRefs(document):
  keyword = RegExp_3gpp_Ref_Definition
  found_refs = findAllByRegularExpression(document, keyword)
  for i in range(0, found_refs.Count):
    text_range = found_refs.getByIndex(i)
    bookmark_name = make3gppRefBookmarkName(i+1)
    replaceBookmark(document, text_range, bookmark_name)

def addBookmarksToHeadings(document):
  def __do_heading(heading_level):
    found_headings = findAllByStyle(document, 'Heading %d' % heading_level)
    for i in range(0, found_headings.Count):
      text_range = found_headings.getByIndex(i)
      heading = text_range.String
      heading = heading.strip()
      if len(heading) == 0:
        continue
      numbering = heading.split()[0]
      bookmark_name = makeHeadingBookmarkName(numbering)
      replaceBookmark(document, text_range, bookmark_name)
  min_heading_level = 2
  max_heading_level = 9
  for i in range(min_heading_level, max_heading_level+1):
    __do_heading(i)

def addBookmarksToFigures(document):
  def __do_bookmark(figure_prefix):
    keyword = figure_prefix + '[:space:]+' + '(' + RegExp_Numbering + ')'
    found_figures = findAllByRegularExpression(document, keyword)
    for i in range(0, found_figures.Count):
      text_range = found_figures.getByIndex(i)
      if text_range.CharWeight > 100.0: # do bold text only
        bookmark_numbering = text_range.String[len(RegExp_Figure_Prefix):]
        bookmark_name = makeFigureBookmarkName(figure_prefix, bookmark_numbering)
        replaceBookmark(document, text_range, bookmark_name, replace_old=False)
  for prefix in (RegExp_Figure_Prefix, RegExp_Table_Prefix):
    __do_bookmark(prefix)

def addHyperLinksTo3gppRefs(document):
  keyword = RegExp_3gpp_Ref
  found_refs = findAllByRegularExpression(document, keyword)
  for i in range(0, found_refs.Count):
    text_range = found_refs.getByIndex(i)
    ref_str = text_range.String
    ref_str = ref_str.lstrip('[')
    ref_str = ref_str.rstrip(']')
    try:
      ref_index = int(ref_str)
    except ValueError:
      continue
    bookmark_name = make3gppRefBookmarkName(ref_index)
    insertHyperLink(document, text_range, bookmark_name)

def addHyperLniksToNumberings(document):
  keyword = RegExp_Numbering
  found_numberings = findAllByRegularExpression(document, keyword)
  for i in range(0, found_numberings.Count):
    text_range = found_numberings.getByIndex(i)
    if 'Heading' in text_range.ParaStyleName: # skip heading text
      continue
    if text_range.CharWeight > 100.0: # skip bold text
      continue
    bookmark_name = makeHeadingBookmarkName(text_range.String)
    if insertHyperLink(document, text_range, bookmark_name) is False:
      for prefix in (RegExp_Figure_Prefix, RegExp_Table_Prefix):
        bookmark_name = makeFigureBookmarkName(prefix, text_range.String)
        if insertHyperLink(document, text_range, bookmark_name) is True:
          break

def process(document):
    fixHeadingOutlineLevel(document)
    fixTableTextWrapMode(document)
    addBookmarksTo3gppRefs(document)
    addBookmarksToHeadings(document)
    addBookmarksToFigures(document)
    addHyperLinksTo3gppRefs(document)
    addHyperLniksToNumberings(document)

def saveAsPdf(document, target_url):
  from com.sun.star.beans import PropertyValue
  property = (
      PropertyValue("Overwrite",0,True,0), 
      PropertyValue( "FilterName" , 0, "writer_pdf_Export" , 0 ),
  )
  document.storeToURL(target_url, property)

def convertToPdf(desktop, source_path):
  print 'start pdf conversion for', source_path
  from urllib import pathname2url
  from os.path import splitdrive, abspath, splitext
  drive, pathname = splitdrive(abspath(source_path))
  source_url = "file:///" + drive + pathname2url(pathname)
  target_url = "file:///" + drive + pathname2url((splitext(pathname)[0] + ".pdf"))
  print 'processing', source_url
  document = None
  try:
    document = desktop.loadComponentFromURL(source_url, "_blank", 0, ())
    process(document)
    saveAsPdf(document, target_url)
    print 'saved pdf to', target_url
  except Exception, e:
    print "Exception [%s] caught, while processing %s " % (str(e), source_url)
  if document is not None:
    document.dispose()

# entry point for to call on command line
def main():
  import sys
  ctxt, desktop = connect(port=2002)
  if len(sys.argv) > 1:
    for arg in sys.argv[1:]:
      convertToPdf(desktop, arg)
    return None
  else:
    document = desktop.getCurrentComponent()
    process(document)
    tryToSyncData(ctxt)
    return document # return document for interactive inspection

# entry point for to call as a macro within OOo
def runMacro():
  document = XSCRIPTCONTEXT.getDocument()
  process(document)

# implement as a uno component so that it can be installed as an extension
class Hyper3GPPLinks(unohelper.Base, XJobExecutor):
  def __init__(self, ctxt):
    self.ctxt = ctxt

  # entry point for to call as a uno component
  def trigger(self, args):
    desktop = self.ctxt.ServiceManager.createInstanceWithContext(
        "com.sun.star.frame.Desktop", self.ctxt )

    document = desktop.getCurrentComponent()

    process(document)

if __name__ == '__main__':
  doc = main() # for to run on command line
else:
  g_exportedScripts = runMacro, # for to run as a macro in openoffice
  g_ImplementationHelper = unohelper.ImplementationHelper() # for to run as an extension
  g_ImplementationHelper.addImplementation(Hyper3GPPLinks, "zh.Hyper3GPPLinks", ("com.sun.star.task.Job",),)

